﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 自制数学工具
{
    /// <summary>
    /// 自制的静态数学工具类，内含八个工具方法
    /// </summary>
    public static class Maths
    {
        /// <summary>
        /// 工具一：求直线延长坐标点
        /// </summary>
        /// <param name="A">二维点A</param>
        /// <param name="B">二维点B</param>
        /// <param name="L">直线AB的延长距离</param>
        /// <returns>AB延长后终点所在的坐标</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Point GetExtendPoint(Point A, Point B, double L)
        {
            if(A.X==B.X && A.Y == B.Y)
            {
                throw new ArgumentException("参数有误，无法构成直线！");
            }
            double x = B.X + L * (B.X - A.X) / Math.Sqrt((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y));
            double y = B.Y + L * (B.Y - A.Y) / Math.Sqrt((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y));
            x = Math.Round(x,2);
            y = Math.Round(y,2);
            return new Point(x, y);
        }

        /// <summary>
        /// 工具二：求二维点通过方向角平移offset后的坐标点
        /// </summary>
        /// <param name="A">起始坐标点A</param>
        /// <param name="offset">平移距离</param>
        /// <returns>位移后坐标点</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Point GetPanningPoint(Point A,double offset)
        {
            if(A.X == 0 && A.Y == 0)
            {
                throw new ArgumentException("不可以用坐标原点作为参数！");
            }
            Point point = new Point();
            if (A.X == 0)
            {
                throw new ArgumentException("A点在Y轴上，无法沿方向角平移！");
            }
            else
            {
                double x = A.X + offset;
                double y = A.Y + offset * A.Y / A.X;
                point.X = x; point.Y = y;
            }
            
            return point;
        }

        /// <summary>
        /// 工具三：求直线AB绕A点逆时针旋转一定度数和半径得到的坐标点
        /// </summary>
        /// <param name="A">坐标点A</param>
        /// <param name="B">坐标点B</param>
        /// <param name="Rotaion_A">旋转角，单位为度数</param>
        /// <param name="R">旋转半径</param>
        /// <returns>逆时针旋转得到的坐标点</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Point GetRotationPoint(Point A,Point B,double Rotaion_A, double R)
        {
            Vector AB = new Vector(A,B);
            if (AB.Length() == 0)
            {
                throw new ArgumentException("参数异常，坐标点重合！");
            }
            double a1 = Math.Atan2(AB.Y,AB.X);
            double a2 = a1 + (Rotaion_A * Math.PI / 180);
            Point point = new Point(A.X,A.Y);

            point.X += R * Math.Cos(a2);
            point.Y += R * Math.Sin(a2);
            point.X = Math.Round(point.X,2);
            point.Y = Math.Round(point.Y,2);
            
            return point;
        }

        /// <summary>
        /// 工具四：求三角形ABC，过A点的单位法向量
        /// </summary>
        /// <param name="A">三角形A点</param>
        /// <param name="B">三角形B点</param>
        /// <param name="C">三角形C点</param>
        /// <returns>过A点的单位法向量坐标</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Vector GetNormalVector(Point A,Point B,Point C)
        {
            Vector AB = new Vector(A, B);
            Vector AC = new Vector(A, C);
            if(AB.Length()==0 || AC.Length() == 0)
            {
                throw new ArgumentException("参数异常，坐标点重合！");
            }
            else
            {
                List<double> list = new List<double>() {AB.X,AB.Y,AB.Z,AC.X,AC.Y,AC.Z };
                for(int i = 0; i < 3; i++) {
                    if (list[i]!=0 && list[i + 3] != 0)
                    {
                        double k1 = list[i]/list[i+3];
                        if (list[3] * k1 == list[0] && list[4]*k1 == list[1] && list[5]*k1  == list[2] )
                        {
                            throw new ArgumentException("参数异常，无法构成平面！");
                        }
                    }
                }
            }
            double x = Math.Round((AB.Y * AC.Z) - (AC.Y * AB.Z), 2);
            double y = Math.Round((AB.Z * AC.X) - (AC.Z * AB.X), 2);
            double z = Math.Round((AB.X * AC.Y) - (AC.X * AB.Y), 2);

            double k = Math.Sqrt(x * x + y * y + z * z);
            x = Math.Round(x / k,3);
            y = Math.Round(y / k,3);
            z = Math.Round(z / k,3);
            return new Vector(x,y,z);
        }

        /// <summary>
        /// 工具五：求AB、CD的交点
        /// </summary>
        /// <param name="A">坐标点A</param>
        /// <param name="B">坐标点B</param>
        /// <param name="C">坐标点C</param>
        /// <param name="D">坐标点D</param>
        /// <returns>交点</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Point GetMeetingPoint(Point A,Point B,Point C,Point D)
        {
            if((A.X==B.X && A.Y==B.Y) || (C.X==D.X && C.Y == D.Y))
            {
                throw new ArgumentException("参数异常，有坐标点重合！");
            }else if((A.X==B.X && C.X == D.X))
            {
                if(A.X == C.X)
                {
                    throw new ArgumentException("重合");
                }
                throw new ArgumentException("平行");
            }else if(A.Y==B.Y && C.Y == D.Y)
            {
                if (A.Y == C.Y)
                {
                    throw new ArgumentException("重合");
                }
                throw new ArgumentException("平行");
            }else if ((B.X - A.X) * (D.Y - C.Y) == (D.X - C.X) * (B.Y - A.Y))
            {
                throw new ArgumentException("平行");
            }
            double k1 = (B.Y - A.Y)/(B.X - A.X); double c1 = A.Y - k1 * A.X;
            double k2 = (D.Y - C.Y)/(D.X - C.X); double c2 = C.Y - k2 * C.X;

            double x = Math.Round((c2 - c1) / (k1 - k2));
            double y = Math.Round(k1 * x - c1);
            return new Point(x,y);

        }

        /// <summary>
        /// 工具六：根据圆心O，半径R，还有圆外一点A，求点A的两个切点
        /// </summary>
        /// <param name="O">圆心</param>
        /// <param name="R">半径</param>
        /// <param name="A">圆外点</param>
        /// <returns>装有两个切点的坐标点数组</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Point[] GetTangentPoint(Point O,double R,Point A)
        {
            if(R <= 0)
            {
                throw new ArgumentException("参数异常，半径必须大于零！");
            }
            double d = Math.Sqrt((A.X - O.X) * (A.X - O.X) + (A.Y - O.Y) * (A.Y - O.Y));
            if(d <= R)
            {
                throw new ArgumentException("参数异常，A点必须在圆的外面！");
            }

            double a = Math.Acos(R / d);
            Point Mid = Maths.GetExtendPoint(A,O,-Math.Cos(a)*R);
            double length = R * Math.Sin(a);
            Point Down = new Point(Mid.X,Mid.Y);
            Point Upper = new Point(Mid.X,Mid.Y);
            if (A.X == O.X)
            {
                Down.X -= length; Upper.X += length;
            }else if(A.Y == O.Y)
            {
                Down.Y -= length; Upper.Y += length;
            }
            else
            {
                double k = (A.X-O.X) / (O.Y -A.Y);
                Down.X -= length / Math.Sqrt(k * k + 1);Down.Y -= length * k / Math.Sqrt(k * k + 1);
                Upper.X += length / Math.Sqrt(k * k + 1);Upper.Y += length * k / Math.Sqrt(k * k + 1);
            }
            Down.X = Math.Round(Down.X,2); Down.Y = Math.Round(Down.Y, 2);
            Upper.X = Math.Round(Upper.X,2); Upper.Y = Math.Round(Upper.Y, 2);
            return new Point[] {Down, Upper};

        }

        /// <summary>
        /// 工具七：根据圆心O，半径R，还有圆外两点A、B构成的直线，求他们的两个交点
        /// </summary>
        /// <param name="O">圆心</param>
        /// <param name="R">半径</param>
        /// <param name="A">坐标点A</param>
        /// <param name="B">坐标点B</param>
        /// <returns>装有两个交点的坐标点数组</returns>
        /// <exception cref="ArgumentException"></exception>
        public static Point[] GetLineMeetingPoint(Point O,double R,Point A,Point B)
        {
            double d = 0;double length = 0;
            Point Mid = new Point();
            Point One = new Point();
            Point Two = new Point();
            if(A.X==B.X && A.Y == B.Y)
            {
                throw new ArgumentException("参数异常，坐标点重合！");
            }else if(A.X==B.X)
            {
                d = A.X - O.X;
                if(d < R)
                {
                    length = Math.Sqrt(R * R - d * d);
                    Two.X = A.X;Two.Y = O.Y + length;
                    One.X = A.X;One.Y = O.Y - length;
                }
                else
                {
                    throw new ArgumentException("参数异常，直线与圆并未相交！");
                }
            }else if(A.Y==B.Y)
            {
                d = A.Y - O.Y;
                if (d < R)
                {
                    length = Math.Sqrt(R * R - d * d);
                    Two.Y = A.Y; Two.X = O.X + length;
                    One.Y = A.Y; One.X = O.X - length;
                }
                else
                {
                    throw new ArgumentException("参数异常，直线与圆并未相交！");
                }
            }
            else
            {
                d = ((B.Y - A.Y) * O.X + (A.X - B.X) * O.Y + (B.X * A.Y - A.X * B.Y)) /
                    Math.Sqrt((B.Y - A.Y) * (B.Y - A.Y) + (A.X - B.X) * (A.X - B.X));
                if (d >= R)
                {
                    throw new ArgumentException("参数异常，直线与圆并未相交！");
                }
                double k1 = (A.Y - B.Y) / (A.X - B.X);
                double k2 = (A.X - B.X) / (B.Y - A.Y);
                Mid.X = d / Math.Sqrt(k2 * k2 + 1) + O.X;Mid.Y = d * k2 / Math.Sqrt(k2 * k2 + 1) + O.Y;
                length = Math.Sqrt(R * R - d * d);
                One.X = Mid.X + length / Math.Sqrt(k1 * k1 + 1);One.Y = Mid.Y + length * k1 / Math.Sqrt(k1 * k1 + 1);
                Two.X = Mid.X - length / Math.Sqrt(k1 * k1 + 1);Two.Y = Mid.Y - length * k1 / Math.Sqrt(k1 * k1 + 1);
            }
            One.X = Math.Round(One.X, 2);
            One.Y = Math.Round(One.Y, 2);
            Two.X = Math.Round(Two.X, 2);
            Two.Y = Math.Round(Two.Y, 2);

            return new Point[] {One,Two};
        }

        /// <summary>
        /// 工具八：根据圆心O、半径R，求圆上的任意一点的切线方向角
        /// </summary>
        /// <param name="O">圆心</param>
        /// <param name="R">半径</param>
        /// <param name="A">在圆上的一点</param>
        /// <returns>点A的切线的方向角度数</returns>
        /// <exception cref="ArgumentException"></exception>
        public static double GetCircleDirection(Point O,double R,Point A)
        {
            if(Math.Round(Math.Sqrt((A.X-O.X)* (A.X - O.X)+ (A.Y - O.Y)* (A.Y - O.Y)))
                != R)
            {
                throw new ArgumentException("参数异常，给出的坐标不在圆上！");
            }
            double a;
            if (A.X == O.X)
            {
                a = 0;
            }
            else if (A.Y == O.Y)
            {
                a = 90;
            }
            else
            {
                a = Math.Atan2(O.X - A.X, A.Y - O.Y);
                a = Math.Round(a * 180 / Math.PI, 2);
                if(a < 0)
                {
                    a = -a;
                }
            }
            return a;
        }
    }
}
